package com.andkrup.comm {
	import com.andkrup.events.LoaderEvent;	
	
	import flash.net.URLLoader;	
	import flash.events.HTTPStatusEvent;	
	import flash.events.SecurityErrorEvent;	
	import flash.events.IOErrorEvent;	
	import flash.events.ProgressEvent;	
	import flash.events.EventDispatcher;	
	import flash.events.Event;	
	
	import com.andkrup.ds.LoaderRequest;	
	import com.andkrup.ds.BundleLoader;	
	import com.andkrup.ds.IResourceLoader;	
	
	import flash.utils.Dictionary;	
	
	import com.adobe.net.URI;

	
	/**
	 * @author Andkrup
	 * 
	 * This class is responsible for loading external resource files and caching
	 * these resources so following requests can load quicker.
	 * 
	 * It uses a Dictionary object to cache resources. The Dictionary object uses
	 * weakKeys, but it would be good practice to handle the cache with memory-
	 * usage in mind anyways.
	 * 
	 * If you need to change the way the ResourceManager loads the resources, you
	 * can change the resourceLoader property
	 * 
	 */
	public class ResourceManager extends EventDispatcher implements ILoadListener {
		private static var _inst:ResourceManager;
		private var uris : Array;
		private var loaderRequests : Array;
		private var cache:Dictionary;
		
		public var preParser:IResourcePreParser;
		
		/**
		 * The object doing the loading. Exchange with a different IResourceLoader
		 * if you need to load resources in a different way, i.e. prioritized or
		 * one at a time.
		 */
		public var resourceLoader:IResourceLoader;

		public static function get instance():ResourceManager{
			if(!_inst){
				_inst = new ResourceManager(new ResourceManagerConstructor());
			}
			return _inst;
		}
		public function ResourceManager(ctr:ResourceManagerConstructor){
			resourceLoader = new BundleLoader();
			preParser = new ResourcePreParser();
			uris = [];
			loaderRequests = [];
			clearCache();
			ctr = undefined;
		}
		
		/**
		 * This method returns the external resource from cache if it has been
		 * previously loaded. If not, it returns null but tries to load the
		 * resource and dispatches a QueueEvent.ITEM_LOADED when the resource is
		 * loaded.
		 */
		public function loadResource(uri:URI):*{
			var lr:LoaderRequest;
			
			if(!resourceLoader){
				throw new Error("resourceLoader is null");
			}
			if(uri.toString().length==0){
				return;
			}
			if(!containsUri(uri)){
				uris.push(uri);
			}
			else{
				// check in cache
				var resource:* = getCached(uri);
				if(null!=resource){
					return resource;
				}
				// check loaderRequests
				for each(lr in loaderRequests){
					if(lr.uri==uri){
						// uri is already loading, caller will get notified by when uri has loaded
						return;
					}
				}
			}
			// all possibilities exempt, load the resource
			lr = resourceLoader.add(uri, this);
			loaderRequests.push(lr);
		}
		
		public function clearCache():void{
			cache = new Dictionary(true);
		}
		public function deleteFromCache(uri:URI):void{
			cache[uri.toString()] = undefined;
		}

		private function getCached(uri:URI) : *{
			return cache[uri.toString()];
		}
		private function cacheResource(uri:URI, resource:*):void{
			cache[uri.toString()] = resource;
		}

		private function containsUri(uri:URI):Boolean{
			for each(var u:URI in uris){
				if(uri==u){
					return true;
				}
			}
			return false;
		}
		private function getLoaderRequestByLoader(loader:URLLoader):LoaderRequest{
			for each(var lr:LoaderRequest in loaderRequests){
				if(lr.loader==loader){
					return lr;
				}
			}
			return null;
		}
		
		public function onLoadComplete(e:Event):void{
			trace("ResourceManager:> onLoadComplete() tgt: "+e.target);
			var l:URLLoader = e.target as URLLoader;
			if(l){
				var lr:LoaderRequest = getLoaderRequestByLoader(l);
				if(lr){
					cacheResource(lr.uri, preParser.castToType(lr.loader.data, lr.uri));
				}
				var str:String = "";
				var c:uint = 0;
				for(var s:String in cache){
					str += "\n cache["+s+"]";
					c++;
				}
				trace("ResourceManager:> onLoadComplete() deleting loaderrequest w. loader: "+l+", lr:"+lr+", cache: "+str);
				dispatchEvent(new LoaderEvent(LoaderEvent.ITEM_COMPLETE, lr.uri, getCached(lr.uri), resourceLoader));
				resourceLoader.remove(lr, this);
				if(c==uris.length){
					dispatchEvent(new LoaderEvent(LoaderEvent.QUEUE_COMPLETE, lr.uri, getCached(lr.uri), resourceLoader));
				}
			}
		}
		public function onLoadOpen(e:Event):void{
		}
		public function onLoadInit(e:Event):void{
		}
		public function onLoadProgress(e:ProgressEvent):void{
		}
		public function onLoadIOError(e:IOErrorEvent):void{
			trace("ResourceLoader:> onLoadIOError() "+e.text);
		}
		public function onLoadSecurityError(e:SecurityErrorEvent):void{
			trace("ResourceLoader:> onLoadSecurityError() "+e.text);
		}
		public function onLoadHttpStatus(e:HTTPStatusEvent):void{
		}
	}
}
class ResourceManagerConstructor{}